use core::any::Any;

use axerrno::LinuxError;
use axfs_ng_vfs::{NodeFlags, VfsResult};
use axplat_aarch64_opi5p::{GpioController, gpio_controller};
use gpio_rk3588::Level as HwLevel;
use sbox_proto::gpio::{self, Level, Request};
use starry_vm::{VmMutPtr, VmPtr};

use crate::vfs::DeviceOps;

/// Character device that exposes the RK3588 GPIO controller to user space.
pub struct GpioDevice;

impl GpioDevice {
    pub const fn new() -> Self {
        Self
    }

    fn controller(&self) -> &'static GpioController {
        gpio_controller()
    }

    fn validate(request: &Request) -> VfsResult<()> {
        if request.bank >= 5 {
            return Err(LinuxError::EINVAL);
        }
        if request.pin >= 32 {
            return Err(LinuxError::EINVAL);
        }
        Ok(())
    }

    fn convert_level(level: Level) -> HwLevel {
        match level {
            Level::Low => HwLevel::Low,
            Level::High => HwLevel::High,
        }
    }

    fn to_proto(level: HwLevel) -> Level {
        match level {
            HwLevel::Low => Level::Low,
            HwLevel::High => Level::High,
        }
    }
}

impl Default for GpioDevice {
    fn default() -> Self {
        Self::new()
    }
}

impl DeviceOps for GpioDevice {
    fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult<usize> {
        Ok(0)
    }

    fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult<usize> {
        Ok(buf.len())
    }

    fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult<usize> {
        let ctrl = self.controller();
        match cmd {
            gpio::IOCTL_SET => {
                let request = unsafe { (arg as *const Request).vm_read_uninit()?.assume_init() };
                Self::validate(&request)?;
                ctrl.set_output(
                    request.bank as usize,
                    request.pin as u8,
                    Self::convert_level(request.level),
                );
            }
            gpio::IOCTL_TOGGLE => {
                let request = unsafe { (arg as *const Request).vm_read_uninit()?.assume_init() };
                Self::validate(&request)?;
                ctrl.toggle(request.bank as usize, request.pin as u8);
            }
            gpio::IOCTL_GET => {
                let ptr = arg as *mut Request;
                if ptr.is_null() {
                    return Err(LinuxError::EINVAL);
                }
                let mut request = unsafe { ptr.vm_read_uninit()?.assume_init() };
                Self::validate(&request)?;
                let level = ctrl.read_input(request.bank as usize, request.pin as u8);
                request.level = Self::to_proto(level);
                ptr.vm_write(request)?;
            }
            _ => return Err(LinuxError::ENOTTY),
        }
        Ok(0)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn flags(&self) -> NodeFlags {
        NodeFlags::NON_CACHEABLE | NodeFlags::STREAM
    }
}
